Linux Kernel image |
您所在的位置:网站首页 › EmuELEC 编译内核 kernelimg › Linux Kernel image |
学习Linux Kernel image in different forms
1.内核镜像介绍 通常一个可启动的内核镜像 (bootable kernel image) 是经过算法压缩的,2.6.30 之后采用 LZMA 或者 BZIP2,vmlinuz 最后的 z 表示内核是压缩的,这也意味着内核中会有一段解压程序。 内核中包含了各种内核镜像的格式,如 vmlinux、zImage、bzImage、uImage 等,首先介绍内核中常见内核文件。 vmlinux是最原始,静态链接的,可执行的,不能bootable的,未压缩的内核镜像。vm代表Virtual Memory。Linux支持虚拟内存,因此得名vm。它是通过源码经过编译汇编, 链接而成的 ELF 文件。 zImage是 vmlinux 经过 gzip 压缩后的文件,适用于小内核。 bzImage 是 vmlinux 经过 gzip 压缩后的文件,适用于大内核,”bz” 表示 “big zImage”。 uImage 是 uboot 专用的镜像文件,它是在 zImage 之前加上一个长度为 0x40 的头信息,包括了该镜像文件的类型、加载位置、生成时间、大小等信息。 2.内核镜像构建 1.vmlinux vmlinux是最原始,未压缩的内核镜像。vm代表Virtual Memory。Linux支持虚拟内存,因此得名vm。它是通过源码经过编译汇编, 链接而成的 ELF 文件。因此这个 vmlinux 文件包含了 ELF 的属性,以及各种调试信息等,因此这个阶段的内核镜像 vmlinux 特别大,而且不能直接在 arm 上直接运行。 2.Image 由于 vmlinux 镜像体积巨大而且不能在 arm 上运行,因此需要使用 objcopy工具将不需要 的 section 从 vmlinux 里面剥离出来,最终在 arch/arm64/boot/目录下生成 Image 文件, 此时 Image 是可以在 arm 平台上运行的,该镜像文件也是未压缩。由于历史原因,当年制作出 Image 的大小正好比一个软盘大一点,为了让内核镜像能够装在一张软盘上,所以就将 Image 进行压缩, 生成 piggy.gz 或者 piggy_data. 3.piggy.gz/piggy_data The file Image compressed with gzip.一开始只支持 gzip 压缩方法,所以将压缩之后的 Image 称为 piggy.gz,但随着内核的不断 发展,内核支持更多的压缩算法,因此把压缩之后的 Image 称为 piggy_data. 4.piggy.o 之前说过 Image 可以在 arm 上运行,当不能直接运行,因为 Image 运行前需要一些已知 初始化环境,这就需要特定功能的代码实现这些功能,这里称这些代码为 bootstrap。 于是内核在 arch/arm/boot/compressed/ 目录下增加了 bootstrap 功能的代码。和制作 vmlinux 一样,需要将这个目录下的源文件编译汇编成目标文件,然后再链接成一个文件。 为了构造这个,内核将 piggy_data 直接塞到了一个汇编文件 piggy.S 中,然后这个文件 经过汇编之后,就生成了 piggy.o。 2.2.vmlinux 构建过程 vmlinux 文件是 Kbuild 编译系统将源码经过编译链接所获得的目标文件,所以它是一个 ELF 文件,因此 vmlinux 文件包含了各种调试信息和各种有用的 section。vmlinux 文件的链接过程由 arch/$(ARCH)/kernel/vmlinux.lds.S 链接脚本决定,可以通过该文件知道 vmlinux 文件的内部布局。 Image 就是通过 vmlinux objcopy 获得,这里 objcopy 对应的命令是 位于 scripts/Makefile.lib 文件中获得,定义如下: quiet_cmd_objcopy = OBJCOPY $@ cmd_objcopy = $(OBJCOPY) $(OBJCOPYFLAGS) $(OBJCOPYFLAGS_$(@F)) $< $@测试:在 Image 生成过程中添加打印消息,以此查看整个 object 过程, 添加调试代码如下: $(obj)/Image: vmlinux FORCE $(warning "OBJCOPYFLAGS: $(OBJCOPYFLAGS)") $(warning "OBJCOPYFLAGS_$(@F): $(OBJCOPYFLAGS_$(@F))") $(call if_changed,objcopy)编译内核时生成如下信息: LD vmlinux SORTEX vmlinux SYSMAP System.map arch/arm/boot/Makefile:61: "OBJCOPYFLAGS: -O binary -R .comment -S" arch/arm/boot/Makefile:61: "OBJCOPYFLAGS_Image: " OBJCOPY arch/arm/boot/Image Building modules, stage 2. MODPOST 6 modules Kernel: arch/arm/boot/Image is readyvmlinux 使用 objcopy变成 Image 时,使用的参数 是 “-O binary -R .comment -S”,这个参数的意思是: -O binary 表示生成二进制文件-R .comment 表示移除 .comment section-S 表示移除所有的标志以及重定位信息2.4.piggy_data构建 piggy_data是 Image 经过压缩之后得到的压缩文件,具体如下: //arch/arm/boot/compressed/Makefile: $(obj)/piggy_data: $(obj)/../Image FORCE $(call if_changed,$(compress-y))通过上面的内容可知,内核采用的压缩方法由 compress-y 变量决定,其定义如下: //arch/arm/boot/compressed/Makefile : compress-$(CONFIG_KERNEL_GZIP) = gzip compress-$(CONFIG_KERNEL_LZO) = lzo compress-$(CONFIG_KERNEL_LZMA) = lzma compress-$(CONFIG_KERNEL_XZ) = xzkern compress-$(CONFIG_KERNEL_LZ4) = lz4因此内核支持 gzip,lzo,lzma,xzkern, 和 lz4 的压缩方法,具体使用哪种,因此开发者可以在 命令执行处添加调试代码如下: $(obj)/piggy_data: $(obj)/../Image FORCE $(wraning "compress-y: $(compress-y)") $(call if_changed,$(compress-y))Image 常采用了gzip 方法,在 scripts/Makefile.lib 文件中获得具体的 gzip 过程,如下: quiet_cmd_gzip = GZIP $@ cmd_gzip = cat $(filter-out FORCE,$^) | gzip -n -f -9 > $@gizp 的参数含义如下: -n 压缩文件时,不保存原来文件名称以及时间戳-f 强制压缩文件。不理会文件名称或硬链接是否存在以及文件是否为符号链接-9 用 9 调整压缩的速度,-1 或 --fast 表示最快压缩方法 (低压缩比), -9 或者 --best 表示最慢的压缩方法 (高压缩比)2.5.Bootstrap ELF kernel (vmlinux) 构建过程 只有纯粹的内核是无法启动的,所以需要在内核的头部加入一些用于 bootstrap loader 功能的代码。 Kbuild 编译系统在 arch/arm/boot/compressed/ 目录下,将 head.S, misc.S, compressed.S 等多个汇编文件汇编成多个可链接的 ELF 目标文件,以此作为内核的 bootstrap loader。在这个步骤,Kbuid 编译系统将这些可链接的目标文件与 piggy.o 文件按链接脚本的内容进行链接,制作出一个带 bootstrap loader 的内核ELF 文件。对于过程要参考 arch/arm/boot/compressed/ 目录下的 Makefile 和 vmlinux.lds.S 文件。 首先通过分析 Makefile 知道链接的文件,具体源码如下: $(obj)/vmlinux: $(obj)/vmlinux.lds $(obj)/$(HEAD) $(obj)/piggy.o \ $(addprefix $(obj)/, $(OBJS)) $(lib1funcs) $(ashldi3) \ $(bswapsdi2) $(efi-obj-y) FORCE @$(check_for_multiple_zreladdr) $(call if_changed,ld) @$(check_for_bad_syms)2.5.1.vmlinux.lds 理解 vmlinux.lds总共有两个,分别是: arch/arm/kernel/vmlinux.lds(用于生成未压缩的内核image)arch/arm/boot/compressed/vmlinux.lds(用于生成经过压缩的内核image)这两个lds 文件中的链接地址区别: arch/arm/kernel/vmlinux.lds: 421 OUTPUT_ARCH(arm) 422 ENTRY(stext) 423 jiffies = jiffies_64; 424 SECTIONS 425 { 426 /* 427 * XXX: The linker does not define how output sections are 428 * assigned to input sections when there are multiple statements 429 * matching the same input section name. There is no documented 430 * order of matching. 431 * 432 * unwind exit sections must be discarded before the rest of the 433 * unwind sections get included. 434 */ 435 /DISCARD/ : { 436 *(.ARM.exidx.exit.text) 437 *(.ARM.extab.exit.text) 438 439 440 441 442 *(.exitcall.exit) 443 *(.alt.smp.init) 444 *(.discard) 445 *(.discard.*) 446 } 447 . = 0xC0000000 + 0x00008000; 448 .head.text : { 449 _text = .; 450 *(.head.text) 451 }arch/arm/boot/compressed/vmlinux.lds: 10 OUTPUT_ARCH(arm) 11 ENTRY(_start) 12 SECTIONS 13 { 14 /DISCARD/ : { 15 *(.ARM.exidx*) 16 *(.ARM.extab*) 17 /* 18 * Discard any r/w data - this produces a link error if we have any, 19 * which is required for PIC decompression. Local data generates 20 * GOTOFF relocations, which prevents it being relocated independently 21 * of the text/got segments. 22 */ 23 *(.data) 24 } 25 26 . = 0; 27 _text = .;压缩内核的链接地址是从0开始的,未压缩的内核链接地址是从0xC0000000+0x00008000开始的。 首先uboot使用到的内核是压缩过的内核也就是uimage,此时内核链接到的是0地址,入口是_start。而arch/arm/boot/compressed/vmlinux.lds中有如下定义: .text : { 30 _start = .; 31 *(.start) 32 *(.text) 33 *(.text.*) 34 *(.fixup) 35 *(.gnu.warning) 36 *(.glue_7t) 37 *(.glue_7) 38 }所以压缩内核的入口就是链接地址是从0开始的。压缩内核来源于压缩的vmlinux,而压缩的vmlinux是通过head.o, misc.o piggy.o加工而来的。这个压缩的vmlinux连接顺序可以从arch/arm/boot/compressed/Makefile中得到如下信息: HEAD = head.o OBJS += misc.o decompress.o vmlinux: $(obj)/vmlinux.lds ( o b j ) / (obj)/ (obj)/(HEAD) ( o b j ) / p i g g y . (obj)/piggy. (obj)/piggy.(suffix_y).o 所以其实压缩内核的入口就是arch/arm/boot/compressed/head.s文件,并且这个文件应该也就是uboot跳转到内核之后的总入口,这部分代码是位置无关,所以链接到0地址也没关系。 内核的入口的这段代码应该还包含搬运内核到高地址空间的代码,搬运的目的地址应该就是arch/arm/kernel/vmlinux.lds文件中的连接地址0xC0000000+0x00008000,这也应该改是内核最终的运行地址。 2.6 zImage构建 zImage 是通过带 bootstrap loader 的内核 ELF 文件经过 objcopy 命令之后制作生成 的二进制文件,用于在 arm 上直接运行,其生成过程可以查看 arch/arm/boot/Makefile: $(obj)/zImage: $(obj)/compressed/vmlinux FORCE $(call if_changed,objcopy)同原始 vmlinux 转换为 Image 过程一致。制作完 zImage 之后, 可以将 zImage 在 arm 上运行。 最终在内存SDRAM中的内核镜像是经过压缩的,只是在运行时再将其解压。所以编译时会先使用gzip将镜像文件image进行压缩,再将压缩后的镜像文件和源码中的两个文件(如下所示)一起链接生成压缩后的镜像文件compress/vmlinux,注意,这两个源码文件是解压程序,用于将内存SDRAM中的压缩镜像zImage进行解压,然后再执行kernel 的第一阶段启动代码arch/arm/kernel/head.S。简而言之,在内存中运行内核时,kernel先自身解压,再执行第一阶段启动代码。 arch/arm/boot/compressed/head.Sarch/arm/boot/compressed/misc.c3.vmlinuz vmlinuz是可引导的、压缩的,能bootable的内核。vmlinux是用来生成vmlinuz的中间步骤。vmlinuz是Linux kernel文件的历史名字,它实际上就是zImage或bzImage。 vmlinuz 的建立有两种方式: 编译内核时执行"make zImage",zImage适用于小内核的情况,它的存在是为了向后的兼容性。 内核编译时通过命令make bzImage创建。 zImage、bzImage 中均包含一个微型的 gzip 用于解压缩内核并引导,两者的不同之处在于: zImage 解压缩内核到低端内存 (第一个640K),bzImage 解压缩内核到高端内存 (1M以上)。也就是,它们之间最大的差别是对于内核体积大小的限制。 由于 zImage 内核需要放在实模式 1MB 的内存之内,所以其体积受到了限制,目前采用的内核格式大多采用的是 bzImage ,这种格式没有 1MB 内存限制。arm 中常用的是 zImage,而 x86 中常用的是 bzImage 。 4.Linux 编译流程图 参考资料: https://www.jianshu.com/p/d4e9b87c409d https://jin-yang.github.io/post/kernel-compile.html https://biscuitos.github.io/blog/ARM-Kernel-Image/ |
今日新闻 |
推荐新闻 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |